package errcode

import (
	"encoding/json"
	"fmt"
	"path"
	"runtime"
)

var codes = map[int]string{}

type AppError struct {
	code     int    `json:"code"`
	msg      string `json:"msg"`
	cause    error  `json:"cause"`
	occurred string `json:"occurred"`
}

func (e *AppError) Error() string {
	if e == nil {
		return ""
	}

	formattedErr := struct {
		Code     int    `json:"code"`
		Msg      string `json:"msg"`
		Cause    string `json:"cause"`
		Occurred string `json:"occurred"`
	}{
		Code:     e.code,
		Msg:      e.msg,
		Occurred: e.occurred,
	}
	if e.cause != nil {
		formattedErr.Cause = e.cause.Error()
	}
	errByte, _ := json.Marshal(formattedErr)
	return string(errByte)
}

func (e *AppError) String() string {
	return e.Error()
}

func (e *AppError) Code() int {
	return e.code
}

func (e *AppError) Msg() string {
	return e.msg
}

func (e *AppError) WithCause(err error) *AppError {
	e.cause = err
	e.occurred = getAppErrOccurredInfo()
	return e
}

func newError(code int, msg string) *AppError {
	if _, ok := codes[code]; ok {
		panic(fmt.Sprintf("错误码 %d 不能重复", code))
	}
	return &AppError{
		code: code,
		msg:  msg,
	}
}

func Wrap(msg string, err error) *AppError {
	if err == nil {
		return nil
	}

	appErr := &AppError{
		code:     -1,
		msg:      msg,
		cause:    err,
		occurred: getAppErrOccurredInfo(),
	}
	return appErr
}

func getAppErrOccurredInfo() string {
	pc, file, line, ok := runtime.Caller(2)
	if !ok {
		return ""
	}
	file = path.Base(file)
	funcName := runtime.FuncForPC(pc).Name()
	triggerInfo := fmt.Sprintf(
		"func: %s, file: %s, line: %d",
		funcName, file, line,
	)
	return triggerInfo
}
